<?php
/**
 * 11abacus Framework's controller
 *
 * PHP version 5
 *
 * LICENSE:
 *
 * Copyright (c) 2008, 11abacus / Philippe Jausions
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of the 11abacus nor the names of its contributors may
 *     be used to endorse or promote products derived from this software
 *     without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @category  Framework
 * @package   Abacus11_Framework
 * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
 * @copyright 2008 11abacus / Philippe Jausions
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
 * @link      http://pear.11abacus.com/package/Abacus11_Framework
 */

/**
 * Required PEAR dependencies
 */
require_once 'HTTP/Header.php';

/**
 * 11abacus Framework's controller
 *
 * @category  Framework
 * @package   Abacus11_Framework
 * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
 * @copyright 2008 11abacus / Philippe Jausions
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD
 * @link      http://pear.11abacus.com/package/Abacus11_Framework
 *
 * @property-read Net_URL_Mapper             $router  The router resolver
 * @property-read Abacus11_Framework_Request $request The request data
 */
abstract class Abacus11_Framework_Controller
{
    /**
     * Singleton instance
     *
     * @var CheapTrips_Framework_Controller
     */
    protected static $instance = null;

    /**
     * Router
     *
     * @var Net_URL_Mapper
     */
    protected $router;

    /**
     * Request data holder
     *
     * @var Abacus11_Framework_Request
     */
    protected $request;
    
    /**
     * Reference to framework instance
     * 
     * @var Abacus11_FrameworkInterface
     */
    protected $framework;

    /**
     * Returns the singleton controller instance
     *
     * @param  string|Abacus11_Framework_Controller $controller type or instance
     * @return Abacus11_Framework_Controller
     */
    static public function singleton($controller = null)
    {
        if (self::$instance === null) {
            if ($controller === null) {
                self::$instance = self::factory(self::guessType());

            } elseif (is_string($controller)) {
                self::$instance = self::factory($controller);

            } elseif ($controller instanceof self) {
                self::$instance = $controller;

            } else {
                throw new Abacus11_Framework_Exception_InvalidArgument('Invalid controller',
                                 Abacus11_Framework::SCOPE_CONTROLLER);
            }
        }

        return self::$instance;
    }

    /**
     * Returns an instance of the controller
     *
     * @param string $type Controller type (Web, CommandLine, SOAP, etc...)
     * 
     * @return Abacus11_Framework_Controller instance
     */
    static protected function factory($type)
    {
        $class = 'Abacus11_Framework_Controller_'.$type;
        $file  = 'Abacus11/Framework/Controller/'.$type.'.php';
        if (!class_exists($class)) {
            $fp = @fopen($file, 'r', true);
            if ($fp === false) {
                throw new Abacus11_Framework_Exception_NotFound('Could not load "'
                                                .$controller.'" controller');
            }
            fclose($fp);
            include_once $file;
        }
        return new $class();
    }

    /**
     * Class constructor
     */
    protected function __construct()
    {
        $this->framework = Abacus11_Framework::singleton();
        $this->router    = Abacus11_Framework_Router::singleton();
        $this->request   = $this->getRequest();
    }

    /**
     * Property getter
     *
     * @param string $name property name
     *
     * @return mixed
     */
    public function __get($name)
    {
        switch ($name) {
        case 'router':
        case 'request':
        case 'framework':
            return $this->$name;
        }

        throw new Abacus11_Framework_Exception_NotFound('No such class member "'
                             .$name.'"', Abacus11_Framework::SCOPE_CONTROLLER);
    }

    /**
     * Property setter
     *
     * @param string $name  Property name
     * @param mixed  $value Property value
     *
     * @return void
     */
    public function __set($name, $value)
    {
        throw new Abacus11_Framework_Exception_NotFound('No writable class member "'
                       .$name.'" found', Abacus11_Framework::SCOPE_CONTROLLER);
    }

    /**
     * Processes the request
     *
     * @return void
     * @todo   Handle errors
     * @todo   Handle redirect 
     */
    public function run()
    {
        try {
            $this->render($this->execute());
        } catch (Exception $e) {
            $this->renderError($e);
        }
    }

    /**
     * Renders the result
     *
     * @param Abacus11_Framework_Response $response Response
     * 
     * @return void
     */
    abstract function render(Abacus11_Framework_Response $response);

    /**
     * Renders an error
     *
     * @param mixed $error Error to render
     * 
     * @return void
     */
    abstract function renderError($error);

    /**
     * Executes an action
     *
     * @param string                     $url     URL to pass for processing an 
     *                                            action on-demand
     * @param Abacus11_Framework_Request $request Data request 
     * @param object                     $user    User
     * 
     * @return Abacus11_Framework_Response Response instance
     */
    public function execute($url = null, 
                            Abacus11_Framework_Request $request = null, 
                            $user = null)
    {
        if ($url === null) {
            static $urlVars = array(
                'REQUEST_URI',
                'HTTP_X_REWRITE_URL',
                'HTTP_URL',
                'HTTP_REQUEST_URI',
            );
            foreach ($urlVars as $urlVar) {
                if (isset($_SERVER[$urlVar])) {
                    $url = $_SERVER[$urlVar];
                    break;
                }
            }
        }
        $module = null;
        $action = null;
        if ($url !== null) {
            $info = $this->router->match($url);
            if ($info !== null 
                && isset($info['module']) && isset($info['action'])) {
                $module = $info['module'];
                $action = $info['action'];
            }
        }
        if ($request === null) {
            $request = $this->request;
        }
        if ($module === null 
            && isset($request->module) && isset($request->action)) {
            $module = $request->module;
            $action = $request->action;
        }
        
        $mod    = $this->framework->getModule($module);
        $status = $mod->getStatus($action, $user, $request); 
        return $mod->getResponse($status, $action, $user, $request);
    }

    /**
     * Attempts to guess a suitable controller type
     * 
     * @return string 
     */
    protected static function guessType()
    {
        if (isset($_SERVER['CONTENT_TYPE'])) {
            switch ($_SERVER['CONTENT_TYPE']) {
            case 'application/soap+xml':
                return 'SOAP';
            }
        }
        if (php_sapi_name() == 'cli') {
            return 'CommandLine';
        }
        if (!empty($_SERVER['REQUEST_METHOD'])) {
            return 'Web';
        }
        throw new Abacus11_Framework_Exception('Could not determine request type',
                                        Abacus11_Framework::SCOPE_REQUEST);
    }

    /**
     * Returns request data
     *
     * @return Abacus11_Framework_Request Request instance
     */
    abstract protected function getRequest();

    /**
     * Returns the URL for the module, action and request
     *
     * @param string|Abacus11_Framework_ModuleInterface $module  Module
     * @param string                                    $action  Action name
     * @param Abacus11_Framework_Request                $request Request
     * 
     * @return string the URL
     */
    public function generateURL($module, $action, $request)
    {
        $params = array_merge($request, array('action' => $action));
        if ($module instanceof Abacus11_Framework_ModuleInterface) {
            $params['module'] = $module->getName();
        } else {
            $params['module'] = $module;
        }
        return $this->router->generate($params);
    }
}

?>